Completed
Branch master (7b91a4)
by Elbert
01:07
created

network.js ➔ ifBrowserValid   B

Complexity

Conditions 4
Paths 7

Size

Total Lines 25

Duplication

Lines 25
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
nc 7
nop 2
dl 25
loc 25
rs 8.5806
c 1
b 0
f 0
1
'use strict';
2
(function() {
3
4
	function isChrome() {
5
		return (typeof chrome !== 'undefined' &&
6
				window.navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9\.]+)/));
7
	}
8
9
	var browserProxy;
10
	if ( isChrome() ) {
11
		browserProxy = chrome;
12
	} else {
13
		browserProxy = browser;
14
	}
15
16
	var MIN_FF_MAJOR_VERSION = 51;
17
18
	var requiredBrowserApis = [
19
		browserProxy.webNavigation,
20
		browserProxy.tabs,
21
		browserProxy.webRequest,
22
		browserProxy.runtime
23
	];
24
	var areListenersRegistered = false;
25
	var secBefore = 2000;
26
	var secAfter = 5000;
27
	var secBetweenDupAssets = 10e3;
28
	var minVidSize = 500e3;
29
	var maxVidSize = 25e6;
30
	var maxContentRange = 25e6;
31
	var videoExtensions = [
32
		'af', '3gp', 'asf', 'avchd', 'avi', 'cam', 'dsh', 'flv', 'm1v', 'm2v',
33
		'fla', 'flr', 'sol', 'm4v', 'mkv', 'wrap', 'mng', 'mov', 'mpeg', 'mpg',
34
		'mpe', 'mp4', 'mxf', 'nsv', 'ogg', 'rm', 'svi', 'smi', 'wmv', 'webm'
35
	];
36
	var extensionsReg = new RegExp('\\.' + videoExtensions.join('$|\\.') + '$');
37
	var videoContentTypesPrefixes = ['binary/octet-stream', 'video/', 'flv-application/', 'media'];
38
39
	var bannedContentTypes = ['video/mp2t','video/f4m','video/f4f'];
40
	var bannedFiletypes = ['ts'];
41
	var bannedFiletypesReg = new RegExp('\\.' + bannedFiletypes.join('$|\\.') + '$');
42
	var whitelistReqTypes = ['object', 'xmlhttprequest', 'other'];
43
44
	var topVideoAssetDomains = [
45
		'2mdn.net',
46
		'adap.tv',
47
		'adnxs.com',
48
		'adsrvr.org',
49
		'btrll.com',
50
		'celtra.com',
51
		'flashtalking.com',
52
		'flite.com',
53
		'innovid.com',
54
		'jivox.com',
55
		'mixpo.com',
56
		'nytimes.com',
57
		'playwire.com',
58
		'selectmedia.asia',
59
		'serving-sys.com',
60
		'solvemedia.com',
61
		'spotible.com',
62
		'teads.tv',
63
		'tribalfusion.com',
64
		'tubemogul.com',
65
		'videologygroup.com',
66
		'washingtonpost.com'
67
	];
68
69
	if ( !String.prototype.endsWith ) {
70
		String.prototype.endsWith = function(searchString, position) {
71
			var subjectString = this.toString();
72
			if ( typeof position !== 'number' || !isFinite(position) ||
73
				Math.floor(position) !== position || position > subjectString.length) {
74
				position = subjectString.length;
75
			}
76
			position -= searchString.length;
77
			var lastIndex = subjectString.indexOf(searchString, position);
78
			return lastIndex !== -1 && lastIndex === position;
79
		};
80
	}
81
82
	function getFrame(getFrameDetails, callback) {
83
		if ( typeof chrome !== 'undefined' ) {
84
			chrome.webNavigation.getFrame(getFrameDetails, callback);
85
		} else if ( typeof browser !== 'undefined' ) {
86
			var gettingFrame = browser.webNavigation.getFrame(getFrameDetails);
87
			gettingFrame.then(callback);
88
		}
89
	}
90
91
	function ifBrowserValid(callback, elseCallback) {
92
		if ( isChrome() ) {
93
94
			callback();
95
		} else if ( typeof browser !== 'undefined' ) {
96
			try {
97
				var gettingInfo = browser.runtime.getBrowserInfo();
98
				gettingInfo.then(function(browserInfo) {
99
					var browserVersion = parseInt(browserInfo.version.split('.')[0]);
100
101
					if ( browserInfo.name === 'Firefox' &&
102
						browserVersion >= MIN_FF_MAJOR_VERSION) {
103
						callback();
104
					} else {
105
						elseCallback();
106
					}
107
				});
108
			} catch (err) {
109
110
				elseCallback();
111
			}
112
		} else {
113
			elseCallback();
114
		}
115
	}
116
117
	function isTrackingEnabled() {
118
119
		return parseInt(localStorage.tracking, 10);
120
121
	}
122
123
	function isPixelRequest(request) {
124
		return (request.type === 'image' || request.responseStatus === 204) &&
125
				request.size <= 1000;
126
	}
127
128
	function isVpaidOrVastRequest(request) {
129
		var lowerCaseUrl = request.url.toLowerCase();
130
		return lowerCaseUrl.indexOf('vpaid') !== -1 || lowerCaseUrl.indexOf('vast') !== -1;
131
	}
132
133
	function hasValidRequestType(request) {
134
		return whitelistReqTypes.indexOf(request.type) >= 0;
135
	}
136
137
	function stripQueryParams(url) {
138
		return url.split('?', 1)[0];
139
	}
140
141
	function parseHostnameFromUrl(url) {
142
		var parser = document.createElement('a');
143
		parser.href = url;
144
		return parser.hostname;
145
	}
146
147
	function hasDomain(url, domain) {
148
		return parseHostnameFromUrl(url).endsWith(domain);
149
	}
150
151
	function findHeader(headers, key) {
152
		var header;
153
		for ( var i = 0; i < headers.length; i += 1 ) {
154
			header = headers[i];
155
			if ( header.name.toLowerCase() === key ) {
156
				return header;
157
			}
158
		}
159
		return null;
160
	}
161
162
	function validVideoType(vtype) {
163
		var goodType = videoContentTypesPrefixes.some(function(prefix) {
164
			return vtype.indexOf(prefix) === 0;
165
		});
166
		return goodType;
167
	}
168
169
	function assetMsgKey(assetReq) {
170
		var url = stripQueryParams(assetReq.url);
171
		var key = assetReq.frameId + '-' + url;
172
		return key;
173
	}
174
175
	var PageNetworkTrafficCollector = function(tabId) {
176
		this.tabId = tabId;
177
		this.displayAdFound = false;
178
		this.requests = {};
179
		this.msgsBeingSent = {};
180
		this.assetsSeen = {};
181
		this.allRedirects = {};
182
	};
183
184
	var globalPageContainer = {
185
		collectors: {},
186
		dyingCollectors: {},
187
188
		cleanupCollector: function(tabId) {
189
			if ( tabId in this.collectors ) {
190
				delete globalPageContainer.collectors[tabId];
191
			}
192
		},
193
194
		onNewNavigation: function(details) {
195
			var tabId = details.tabId;
196
			this.cleanupCollector(tabId);
197
198
			if ( isTrackingEnabled() ) {
199
				if ( !areListenersRegistered ) {
200
201
					registerListeners();
202
				}
203
				this.collectors[tabId] = new PageNetworkTrafficCollector(tabId);
204
			} else {
205
				if ( areListenersRegistered ) {
206
207
					unregisterListeners();
208
				}
209
			}
210
		},
211
212
		onNavigationCommitted: function(details) {
213
214
		},
215
216
		onNavigationCompleted: function(details) {
217
218
		},
219
220
		onTabClose: function(tabId, closeInfo) {
221
222
			this.cleanupCollector(tabId);
223
			delete this.collectors[tabId];
224
		},
225
226
		onDisplayAdFound: function(tabId) {
227
			this.collectors[tabId].displayAdFound = true;
228
		},
229
230
		getRandId: function() {
231
			return String(Math.floor(Math.random() * 1e9));
232
		},
233
234
		getCollector: function(tabId) {
235
			if ( this.collectors.hasOwnProperty(tabId) ) {
236
				return this.collectors[tabId];
237
			}
238
			return null;
239
		},
240
241
		forwardCall: function(details, collectorMemberFunction) {
242
			var collector = this.getCollector(details.tabId);
243
			if ( collector !== null ) {
244
				collectorMemberFunction.apply(collector, [details]);
245
			}
246
		}
247
	};
248
249
	PageNetworkTrafficCollector.prototype.sendLogMessageToTabConsole = function() {
250
		var logMessage = Array.from(arguments).join(' ');
251
		var message = {message: logMessage, event: 'console-log-message'};
252
		browserProxy.tabs.sendMessage(this.tabId, message);
253
	};
254
255
	PageNetworkTrafficCollector.prototype.sendToTab = function(assetReq, reqs, curPageUrl, isValidAd) {
256
		var msg = {};
257
		msg.assets = [];
258
		msg.event_data = {};
259
		if ( isValidAd ) {
260
			msg.event = 'new-video-ad';
261
			msg.requests = reqs;
262
			msg.requests.sort(function(reqA, reqB) {return reqA.requestTimestamp - reqB.requestTimestamp;});
263
			if ( assetReq ) {
264
				msg.assets = [assetReq];
265
			}
266
		} else {
267
			msg.requests = reqs.map(function(request) {
268
				return parseHostnameFromUrl(request.url);
269
			});
270
			msg.assets = [{
271
272
				url: parseHostnameFromUrl(assetReq.url),
273
274
				contentType: assetReq.contentType,
275
				size: assetReq.size
276
			}];
277
			msg.event = 'new-invalid-video-ad';
278
		}
279
		msg.origUrl = curPageUrl;
280
		msg.displayAdFound = this.displayAdFound;
281
282
		browserProxy.tabs.sendMessage(this.tabId, msg);
283
	};
284
285
	PageNetworkTrafficCollector.prototype.getRedirKey = function(url, frameId) {
286
		return url + ':' + frameId;
287
	};
288
289
	PageNetworkTrafficCollector.prototype.seenBefore = function(request) {
290
		var oldTime = this.assetsSeen[assetMsgKey(request)];
291
		if ( oldTime && (request.requestTimestamp-oldTime < secBetweenDupAssets)){
292
293
			return true;
294
		}
295
		return false;
296
	};
297
298
	PageNetworkTrafficCollector.prototype.recordSeenAsset = function(request) {
299
		this.assetsSeen[assetMsgKey(request)] = request.requestTimestamp;
300
	};
301
302
	PageNetworkTrafficCollector.prototype.onBeforeRequest = function(details) {
303
		var req = {
304
			url: details.url,
305
			type: details.type,
306
			httpMethod: details.method,
307
			frameId: details.frameId,
308
			parentFrameId: details.parentFrameId,
309
			requestTimestamp: details.timeStamp,
310
		};
311
		this.requests[details.requestId] = req;
312
	};
313
314
	PageNetworkTrafficCollector.prototype.onSendHeaders = function(details) {
315
		var request, header;
316
		request = this.requests[details.requestId];
317
		header = request && findHeader(details.requestHeaders, 'x-requested-with');
318
		if ( header && header.value.toLowerCase().indexOf('flash') > -1 ) {
319
			request.from_flash = true;
320
		}
321
	};
322
323
	PageNetworkTrafficCollector.prototype.onHeadersReceived = function(details) {
324
		var getFrameDetails = {
325
			tabId: details.tabId,
326
			processId: null,
327
			frameId: details.frameId
328
		};
329
		var pageNetworkTrafficController = this;
330
		getFrame(getFrameDetails, function(frameDetails) {
331
			if ( frameDetails && frameDetails.url ) {
332
				pageNetworkTrafficController._onHeadersReceived(details, frameDetails);
333
			}
334
		});
335
	};
336
337
	PageNetworkTrafficCollector.prototype._onHeadersReceived = function(details, frameDetails) {
338
		var contentSize, contentRange;
339
340
		var request = this.requests[details.requestId];
341
		if ( request ) {
342
			var redirParent = this.allRedirects[this.getRedirKey(details.url, details.frameId)];
343
			var header = request && findHeader(details.responseHeaders, 'content-type');
344
			var contentType = header && header.value.toLowerCase();
345
346
			if ( contentType){
347
				request.contentType = contentType;
348
			}
349
			header = request && findHeader(details.responseHeaders, 'content-length');
350
			contentSize = header && header.value;
351
			if ( contentSize ) {
352
				request.size = request.size || 0;
353
				request.size += parseInt(contentSize);
354
			}
355
			header = request && findHeader(details.responseHeaders, 'content-range');
356
			contentRange = header && header.value;
357
			if ( contentRange ) {
358
				request.contentRange = parseInt(contentRange.split('/')[1]);
359
			}
360
361
			var frameUrl = null;
362
			if ( frameDetails && frameDetails.url ) {
363
				frameUrl = frameDetails.url;
364
			}
365
			if ( !this.bannedRequest(request) &&
366
				(this.isVideoReq(frameUrl, request) || (redirParent && redirParent.isVideo))) {
367
				request.isVideo = true;
368
			}
369
		}
370
	};
371
372
	PageNetworkTrafficCollector.prototype.onBeforeRedirect = function(details) {
373
		var request = this.requests[details.requestId];
374
		if ( request ) {
375
			if ( request.redirects ) {
376
				request.redirects.push(details.redirectUrl);
377
			} else {
378
				request.redirects = [details.redirectUrl];
379
			}
380
			this.allRedirects[this.getRedirKey(details.redirectUrl, details.frameId)] = request;
381
		}
382
	};
383
384
	PageNetworkTrafficCollector.prototype.isYoutubeMastheadRequest = function(url) {
385
		var re = /video_masthead/;
386
		return this.hasYoutubeDomain(url) && re.test(url);
387
	};
388
	PageNetworkTrafficCollector.prototype.isYoutubeVideoRequest = function(srcUrl, destUrl) {
389
		if ( !this.hasYoutubeDomain(srcUrl) ) {
390
			return false;
391
		}
392
393
		var re = /https?:\/\/r.*?\.googlevideo\.com\/videoplayback\?/;
394
		return re.test(destUrl);
395
	};
396
	PageNetworkTrafficCollector.prototype.processResponse = function(requestDetails, frameDetails) {
397
		var request;
398
		if ( requestDetails ) {
399
			request = this.requests[requestDetails.requestId];
400
			if ( request ) {
401
				request.responseStatus = requestDetails.statusCode;
402
				request.responseTimestamp = requestDetails.timeStamp;
403
404
				var frameUrl = null;
405
				if ( frameDetails && frameDetails.url ) {
406
					frameUrl = frameDetails.url;
407
				}
408
409
				var requestUrl = null;
410
				if ( request.url ) {
411
					requestUrl = request.url;
412
				}
413
414
				if ( this.isYoutubeAdReq(frameUrl, requestUrl) ) {
415
					var videoId = this.parseYoutubeVideoIdFromUrl(requestUrl);
416
					if ( videoId ) {
417
						request.isYoutubeAd = true;
418
						request.isVideo = true;
419
						request.url = 'https://www.youtube.com/watch?v=' + this.parseYoutubeVideoIdFromUrl(requestUrl);
420
					}
421
				} else if ( !this.bannedRequest(request) &&
422
						(this.isVideo || this.isVideoReq(frameUrl, request))) {
423
					request.isVideo = true;
424
				}
425
426
				if ( request.isVideo ) {
427
428
					var msgKey = assetMsgKey(request);
429
					this.msgsBeingSent[msgKey] = request;
430
					if ( !this.seenBefore(request) ) {
431
						this.sendMsgWhenQuiet(msgKey);
432
					}
433
					this.recordSeenAsset(request);
434
				}
435
			}
436
		}
437
	};
438
439
	PageNetworkTrafficCollector.prototype.onResponseStarted = function(responseDetails) {
440
		if ( responseDetails.frameId < 0 ) {
441
			responseDetails.frameId = 99999;
442
443
		}
444
		var getFrameDetails = {
445
			tabId: responseDetails.tabId,
446
			processId: null,
447
			frameId: responseDetails.frameId
448
		};
449
		var pageNetworkTrafficController = this;
450
		getFrame(getFrameDetails, function(frameDetails) {
451
			if ( frameDetails && frameDetails.url ) {
452
				pageNetworkTrafficController.processResponse(responseDetails, frameDetails);
453
			}
454
		});
455
	};
456
457
	PageNetworkTrafficCollector.prototype.hasBannedFiletype = function(request) {
458
		var url = stripQueryParams(request.url);
459
		if ( bannedFiletypesReg.exec(url) ) {
460
			return true;
461
		} else {
462
			return false;
463
		}
464
	};
465
466
	PageNetworkTrafficCollector.prototype.checkContentHeaders = function(request) {
467
		if ( request.contentType && validVideoType(request.contentType) ) {
468
			return true;
469
		}
470
		return false;
471
	};
472
473
	PageNetworkTrafficCollector.prototype.checkUrlExtension = function(request) {
474
		var url = stripQueryParams(request.url);
475
		if ( extensionsReg.exec(url) ) {
476
			return true;
477
		} else {
478
			return false;
479
		}
480
	};
481
482
	PageNetworkTrafficCollector.prototype.isVideoReq = function(srcUrl, request) {
483
		if ( this.isYoutubeVideoRequest(srcUrl, request.url) ) {
484
			return false;
485
		}
486
		return this.checkUrlExtension(request) || this.checkContentHeaders(request);
487
	};
488
	PageNetworkTrafficCollector.prototype.hasYoutubeDomain = function(url) {
489
		var hostname = parseHostnameFromUrl(url) ;
490
		if ( hostname === 'www.youtube.com' ) {
491
			return true;
492
		}
493
		return false;
494
	};
495
	PageNetworkTrafficCollector.prototype.parseYoutubeVideoIdFromUrl = function(url) {
496
		var re = /^https?:\/\/www\.youtube\.com\/get_video_info.*(?:\?|&)video_id=(.*?)(?:$|&)/;
497
		var match = re.exec(url);
498
		if ( match && match.length > 1 ) {
499
			return match[1];
500
		}
501
502
		re = /^https?:\/\/www\.youtube\.com\/embed\/(.*?)(?:$|\?)/;
503
		match = re.exec(url);
504
		if ( match && match.length > 1 ) {
505
			return match[1];
506
		}
507
508
		re = /^https?:\/\/www\.youtube\.com\/watch\?v=(.*$)/;
509
		match = re.exec(url);
510
		if ( match && match.length > 1 ) {
511
			return match[1];
512
		}
513
		return null;
514
	};
515
516
	PageNetworkTrafficCollector.prototype.isYoutubeGetVideoInfoReq = function(url) {
517
		var re = /^https?:\/\/www\.youtube\.com\/get_video_info\?/;
518
		return re.test(url);
519
	};
520
	PageNetworkTrafficCollector.prototype.isYoutubeAdReq = function(srcUrl, destUrl) {
521
522
		if ( !this.hasYoutubeDomain(srcUrl) ||
523
			!this.isYoutubeGetVideoInfoReq(destUrl)) {
524
			return false;
525
		}
526
		if ( this.parseYoutubeVideoIdFromUrl(srcUrl) ===
527
			this.parseYoutubeVideoIdFromUrl(destUrl) &&
528
			!this.isYoutubeMastheadRequest(destUrl)) {
529
			return false;
530
		}
531
		return true;
532
	};
533
534
	PageNetworkTrafficCollector.prototype.bannedRequest = function(request) {
535
		return this.bannedVideoType(request) || this.hasBannedFiletype(request) || this.bannedVideoSize(request);
536
	};
537
538
	PageNetworkTrafficCollector.prototype.bannedVideoType = function(request) {
539
		var badType = false;
540
		if ( request.contentType ) {
541
			badType = bannedContentTypes.some(function(prefix) {
542
				return request.contentType.indexOf(prefix) >= 0;
543
			});
544
		}
545
		return badType;
546
	};
547
548
	PageNetworkTrafficCollector.prototype.bannedVideoSize = function(request) {
549
		if ( request.size !== null ) {
550
			if ( request.size < minVidSize || request.size > maxVidSize || request.contentRange > maxContentRange ) {
551
				return true;
552
			}
553
		}
554
		return false;
555
	};
556
557
	PageNetworkTrafficCollector.prototype.grabTagReqs = function(tabRequests, assetRequest) {
558
		var minTimestamp, maxTimestamp;
559
		minTimestamp = assetRequest.requestTimestamp - secBefore;
560
		maxTimestamp = assetRequest.requestTimestamp + secAfter;
561
562
		var filteredRequests = tabRequests.filter(function(request) {
563
			return (request.requestTimestamp > minTimestamp &&
564
				request.requestTimestamp < maxTimestamp &&
565
				request.frameId === assetRequest.frameId &&
566
				request.url !== assetRequest.url &&
567
				(hasValidRequestType(request) ||
568
				isPixelRequest(request)));
569
		});
570
571
		return filteredRequests;
572
	};
573
574
	PageNetworkTrafficCollector.prototype.isValidVideoAd = function(assetRequest, tagRequests) {
575
		var hasVpaidOrVastRequest = tagRequests.some(function(tagRequest) {
576
			return isVpaidOrVastRequest(tagRequest);
577
		});
578
579
		if ( assetRequest.isYoutubeAd ) {
580
			return true;
581
		}
582
		if ( hasVpaidOrVastRequest ) {
583
			return true;
584
		}
585
		var hasTopVideoAssetDomain = topVideoAssetDomains.some(function(assetDomain) {
586
			return hasDomain(assetRequest.url, assetDomain);
587
		});
588
589
		return hasTopVideoAssetDomain;
590
	};
591
592
	PageNetworkTrafficCollector.prototype.sendMsgWhenQuiet = function(msgKey) {
593
		var _this = this,
594
			origPageUrl, msgAssetReq;
595
		msgAssetReq = this.msgsBeingSent[msgKey];
596
		browserProxy.tabs.get(this.tabId, function(tab) {origPageUrl = tab.url;});
597
598
		setTimeout(function() {
599
			var rawRequests = [];
600
			if ( globalPageContainer.collectors[_this.tabId] === _this ) {
601
				for ( var reqId in _this.requests ) {
602
					rawRequests.push(_this.requests[reqId]);
603
				}
604
				var tagReqs = _this.grabTagReqs(rawRequests, msgAssetReq);
605
606
				if ( _this.isValidVideoAd(msgAssetReq, tagReqs) ) {
607
					_this.sendToTab(msgAssetReq, tagReqs, origPageUrl, true);
608
				} else {
609
610
					_this.sendToTab(msgAssetReq, tagReqs, origPageUrl, false);
611
				}
612
613
			} else {
614
615
			}
616
			delete _this.msgsBeingSent[msgKey];
617
		}, secAfter+secBefore);
618
	};
619
620
	PageNetworkTrafficCollector.prototype.existingMessage = function(candidateRequest) {
621
		var frameMsg = this.msgsBeingSent[candidateRequest.frameId];
622
		if ( frameMsg ) {
623
			return frameMsg;
624
		} else {
625
			return null;
626
		}
627
	};
628
629
	function onBeforeRequestListener(details) {
630
		globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onBeforeRequest);
631
	}
632
633
	function onSendHeadersListener(details) {
634
		globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onSendHeaders);
635
	}
636
637
	function onHeadersReceivedListener(details) {
638
		globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onHeadersReceived);
639
	}
640
641
	function onBeforeRedirectListener(details) {
642
		globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onBeforeRedirect);
643
	}
644
645
	function onResponseStartedListener(details) {
646
		globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onResponseStarted);
647
	}
648
649
	function onCommittedListener(details) {
650
		if ( details.frameId === 0 ) {
651
			globalPageContainer.onNavigationCommitted(details);
652
		}
653
	}
654
655
	function onCompletedListener(details) {
656
		if ( details.frameId === 0 ) {
657
			globalPageContainer.onNavigationCompleted(details);
658
		}
659
	}
660
661
	function onRemovedListener(tabId, closeInfo) {
662
		globalPageContainer.onTabClose(tabId, closeInfo);
663
	}
664
665
	function onMessageListener(message, sender, sendResponse) {
666
		if ( message.event === 'new-ad' && message.data.event === 'ad' ) {
667
			var tabId = sender.tab.id;
668
			if ( tabId ) {
669
				globalPageContainer.onDisplayAdFound(tabId);
670
			}
671
		}
672
	}
673
674
	function registerListeners() {
675
676
		browserProxy.webRequest.onBeforeRequest.addListener(
677
			onBeforeRequestListener,
678
			{urls: ['http://*/*', 'https://*/*']},
679
			[]
680
		);
681
682
		browserProxy.webRequest.onSendHeaders.addListener(
683
			onSendHeadersListener,
684
			{urls: ['http://*/*', 'https://*/*']},
685
			['requestHeaders']
686
		);
687
688
		browserProxy.webRequest.onHeadersReceived.addListener(
689
			onHeadersReceivedListener,
690
			{urls: ['http://*/*', 'https://*/*']},
691
			['responseHeaders']
692
		);
693
694
		browserProxy.webRequest.onBeforeRedirect.addListener(
695
			onBeforeRedirectListener,
696
			{urls: ['http://*/*', 'https://*/*']},
697
			[]
698
		);
699
700
		browserProxy.webRequest.onResponseStarted.addListener(
701
			onResponseStartedListener,
702
			{urls: ['http://*/*', 'https://*/*']},
703
			['responseHeaders']
704
		);
705
706
		browserProxy.webNavigation.onCommitted.addListener(onCommittedListener);
707
		browserProxy.webNavigation.onCompleted.addListener(onCompletedListener);
708
		browserProxy.tabs.onRemoved.addListener(onRemovedListener);
709
		browserProxy.runtime.onMessage.addListener(onMessageListener);
710
711
		areListenersRegistered = true;
712
	}
713
714
	function unregisterListeners() {
715
716
		browserProxy.webRequest.onBeforeRequest.removeListener(
717
			onBeforeRequestListener
718
		);
719
720
		browserProxy.webRequest.onSendHeaders.removeListener(
721
			onSendHeadersListener
722
		);
723
724
		browserProxy.webRequest.onHeadersReceived.removeListener(
725
			onHeadersReceivedListener
726
		);
727
728
		browserProxy.webRequest.onBeforeRedirect.removeListener(
729
			onBeforeRedirectListener
730
		);
731
732
		browserProxy.webRequest.onResponseStarted.removeListener(
733
			onResponseStartedListener
734
		);
735
736
		browserProxy.webNavigation.onCommitted.removeListener(onCommittedListener);
737
		browserProxy.webNavigation.onCompleted.removeListener(onCompletedListener);
738
		browserProxy.tabs.onRemoved.removeListener(onRemovedListener);
739
		browserProxy.runtime.onMessage.removeListener(onMessageListener);
740
741
		areListenersRegistered = false;
742
	}
743
744
	function areRequiredBrowserApisAvailable() {
745
		return requiredBrowserApis.every(function(api) {
746
			return typeof api !== 'undefined';
747
		});
748
	}
749
750
	if ( areRequiredBrowserApisAvailable() ) {
751
			ifBrowserValid(
752
				function() {
753
					browserProxy.webNavigation.onBeforeNavigate.addListener(
754
						function(details) {
755
							if ( details.frameId === 0 ) {
756
								globalPageContainer.onNewNavigation(details);
757
							}
758
						},
759
						{
760
							url: [{urlMatches: 'http://*/*'}, {urlMatches: 'https://*/*'}]
761
						}
762
					);
763
				}, function() {
764
765
				}
766
			);
767
	}
768
769
	browserProxy.runtime.onMessage.addListener(function(request, sender, sendResponse) {
770
		if ( request === 'is_browser_valid' ) {
771
			ifBrowserValid(
772
				sendResponse({'browser_valid': true}),
773
				sendResponse({'browser_valid': false})
774
			);
775
		}
776
	});
777
778
	browserProxy.runtime.onMessage.addListener(function(request, sender, sendResponse) {
779
		if ( request === 'is_tracking_enabled' ) {
780
			sendResponse({'tracking_enabled': isTrackingEnabled()});
781
		}
782
	});
783
784
})();
785